package com.itstudio.portal.service.impl;

import com.alibaba.fastjson2.JSONObject;
import com.aliyun.dysmsapi20170525.Client;
import com.aliyun.dysmsapi20170525.models.SendSmsResponse;
import com.aliyun.tea.TeaException;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.itstudio.portal.entity.*;
import com.itstudio.portal.entity.dto.*;
import com.itstudio.portal.entity.vo.*;
import com.itstudio.portal.enums.HttpCodeEnum;
import com.itstudio.portal.exception.SystemException;
import com.itstudio.portal.mapper.*;
import com.itstudio.portal.service.UserService;
import com.itstudio.portal.utils.BeanCopyUtil;
import com.itstudio.portal.utils.Const;
import com.itstudio.portal.utils.JwtUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;


/**
 * (User)表服务实现类
 *
 * @author feng
 * @since 2024-11-11 14:51:27
 */
@Service("userService")
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService  {

    @Autowired
    private AuthenticationManager manager;

    @Autowired
    private UserRolePermMapper userRolePermMapper;

    @Autowired
    StringRedisTemplate redisTemplate;

    @Autowired
    JwtUtil jwtUtil;

    @Autowired
    Client client;

    @Autowired
    PermMapper permMapper;

    @Autowired
    AuthInfoMapper authInfoMapper;

    @Autowired
    RoleMapper roleMapper;

    @Value("${aliyun.sign}")
    private String sign;

    @Value("${aliyun.templateCode}")
    private String templateCode;

    @Autowired
    BCryptPasswordEncoder passwordEncoder;


    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        LambdaQueryWrapper<User> wrapper =  new LambdaQueryWrapper<>();
        wrapper.eq(User::getPhone, username);
        User user = getOne(wrapper);
        if(Objects.isNull(user))
            throw new SystemException(HttpCodeEnum.LOGIN_ERROR);
        if(user.getStatus().equals(Const.STATUS_ONE))
            throw new SystemException(HttpCodeEnum.USER_LOCKED);
        if(user.getType().equals("3")){
            List<String> perms = permMapper.getPerms(user.getId());
            return new UserInfo(user, perms);
        }
        return new UserInfo(user, new ArrayList<>());
    }

    @Override
    public RestResponse loginByCode(LoginCodeDto login) {
        UsernamePasswordAuthenticationToken authenticationToken =
                new UsernamePasswordAuthenticationToken(login.getPhone(), login.getCode());
        Authentication authenticate = manager.authenticate(authenticationToken);
        if(Objects.isNull(authenticate)){
            throw new SystemException(HttpCodeEnum.PHONE_OR_CODE_ERROR);
        }

        UserInfo userInfo = (UserInfo) authenticate.getPrincipal();
        User user = userInfo.getUser();
        String token = jwtUtil.createToken(userInfo);
        Date expireTime = jwtUtil.getExpireTime(token);
        redisTemplate.opsForValue().set(Const.LOGIN + user.getId(), JSONObject.toJSONString(userInfo),
                expireTime.getTime()-new Date().getTime(), TimeUnit.MILLISECONDS);
        redisTemplate.opsForValue().set(Const.TOKEN+user.getId(), token,
                expireTime.getTime()-new Date().getTime(), TimeUnit.MILLISECONDS);
        LoginVo loginVo = BeanCopyUtil.classVo(user, LoginVo.class);
        loginVo.setPerms(userInfo.getPermissions());
        loginVo.setToken(token);
        loginVo.setExpire(BigInteger.valueOf(expireTime.getTime()));
        return RestResponse.success(loginVo);
    }

    @Override
    public boolean sendCode(String phone, Integer code) {
        com.aliyun.dysmsapi20170525.models.SendSmsRequest sendSmsRequest = new com.aliyun.dysmsapi20170525.models.SendSmsRequest()
                .setSignName(sign)
                .setTemplateCode(templateCode)
                .setPhoneNumbers(phone)
                .setTemplateParam("{\"code\":\""+code+"\"}");
        com.aliyun.teautil.models.RuntimeOptions runtime = new com.aliyun.teautil.models.RuntimeOptions();
        try {
            SendSmsResponse result = client.sendSmsWithOptions(sendSmsRequest, runtime);
            return result.statusCode == 200;
        } catch (TeaException error) {
            return false;
        } catch (Exception _error) {
            return false;
        }
    }

    @Override
    public RestResponse register(RegisterDto register) {
        if(!StringUtils.hasText(register.getBodyName()))
            return RestResponse.failure(HttpCodeEnum.BODY_NAME_NOT_NULL);
        if(!StringUtils.hasText(register.getUnifyCreditCode()) || register.getUnifyCreditCode().length() != 18)
            return RestResponse.failure(HttpCodeEnum.UNIFY_CREDIT_ERROR);
        if(!StringUtils.hasText(register.getPhone()))
            return RestResponse.failure(HttpCodeEnum.PHONE_NOT_NULL);
        if(!StringUtils.hasText(register.getPassword()))
            return RestResponse.failure(HttpCodeEnum.PASSWORD_NOT_NULL);
        if(!StringUtils.hasText(register.getCode()))
            return RestResponse.failure(HttpCodeEnum.CODE_NOT_NULL);
        String rCode = redisTemplate.opsForValue().get(Const.REGISTER_CODE + register.getPhone());
        // 校验验证码是否匹配
        if (!StringUtils.hasText(rCode) || !rCode.equals(register.getCode()))
            throw new SystemException(HttpCodeEnum.CODE_ERROR);
        redisTemplate.delete(Const.REGISTER_CODE + register.getPhone());
        User user = BeanCopyUtil.classVo(register, User.class);
        user.setPassword(passwordEncoder.encode(register.getPassword()));
        getBaseMapper().AUTO_INCREMENT();
        save(user);
        return RestResponse.success();
    }

    @Override
    public RestResponse logout(HttpServletRequest request) {
        String token = request.getHeader("Authorization").substring(7);
        String id = jwtUtil.verifyToken(token);
        UserInfo userInfo = (UserInfo)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        if(Objects.nonNull(userInfo)){
            redisTemplate.delete(Const.LOGIN + id);
            redisTemplate.delete(Const.TOKEN+id);
            return RestResponse.success();
        }
        return RestResponse.failure(HttpCodeEnum.NEED_LOGIN);
    }
//查询个人信息
    @Override
    public RestResponse selectUser(Long id) {
        User user = getById(id);
        //判断用户是否认证
        //认证通过的
        if(user.getType().equals(Const.USERTYPE_THREE)){
            List<UserRolePermInfoVo> userRolePermInfoVos = userRolePermMapper.selectUser_Role_PermInfo(id);
            return RestResponse.success(userRolePermInfoVos);
        }
        //没有进行认证的
        UserInfoVo userInfoVo = BeanCopyUtil.classVo(user, UserInfoVo.class);
        return RestResponse.success(userInfoVo);
    }

    @Override
    public RestResponse loginByPasswd(LoginPasswdDto login) {
        UsernamePasswordAuthenticationToken authenticationToken =
                new UsernamePasswordAuthenticationToken(login, null);
        Authentication authenticate = manager.authenticate(authenticationToken);
        if(Objects.isNull(authenticate)){
            throw new SystemException(HttpCodeEnum.PHONE_OR_CODE_ERROR);
        }

        UserInfo userInfo = (UserInfo) authenticate.getPrincipal();
        User user = userInfo.getUser();
        String token = jwtUtil.createToken(userInfo);
        Date expireTime = jwtUtil.getExpireTime(token);
        redisTemplate.opsForValue().set(Const.LOGIN + user.getId(), JSONObject.toJSONString(userInfo),
                expireTime.getTime()-new Date().getTime(), TimeUnit.MILLISECONDS);
        redisTemplate.opsForValue().set(Const.TOKEN+user.getId(), token,
                expireTime.getTime()-new Date().getTime(), TimeUnit.MILLISECONDS);
        LoginVo loginVo = BeanCopyUtil.classVo(user, LoginVo.class);
        loginVo.setPerms(userInfo.getPermissions());
        loginVo.setToken(token);
        loginVo.setExpire(BigInteger.valueOf(expireTime.getTime()));
        return RestResponse.success(loginVo);
    }

    public boolean phoneExist(String phone){
        LambdaQueryWrapper<User> wrapper =  new LambdaQueryWrapper<>();
        wrapper.eq(User::getPhone, phone);
        long count = count(wrapper);
        return count > 0;
    }

    @Override
    public RestResponse forgetPassword(ForGetPasswordDto forget) {
        if(!StringUtils.hasText(forget.getPhone()))
            return RestResponse.failure(HttpCodeEnum.PHONE_NOT_NULL);
        if(!phoneExist(forget.getPhone()))
            return RestResponse.failure(HttpCodeEnum.PHONE_NOT_EXIST);
        if(!StringUtils.hasText(forget.getPassword()))
            return RestResponse.failure(HttpCodeEnum.PASSWORD_NOT_NULL);
        if(!StringUtils.hasText(forget.getCode()))
            return RestResponse.failure(HttpCodeEnum.CODE_NOT_NULL);
        String rCode = redisTemplate.opsForValue().get(Const.FORGOT_CODE + forget.getPhone());
        // 校验验证码是否匹配
        if (!StringUtils.hasText(rCode) || !rCode.equals(forget.getCode()))
            throw new SystemException(HttpCodeEnum.CODE_ERROR);
        redisTemplate.delete(Const.FORGOT_CODE + forget.getPhone());
        LambdaQueryWrapper<User> wrapper =  new LambdaQueryWrapper<>();
        wrapper.eq(User::getPhone, forget.getPhone());
        User user = getOne(wrapper);
        user.setPassword(passwordEncoder.encode(forget.getPassword()));
        updateById(user);
        return RestResponse.success();
    }
}

